/**
 *  @file MemReleasePool.c
 */
#include <stdio.h>
#if defined(WIN32)
#   include <malloc.h>
#   include <windows.h>
#elif defined(linux)
#   include <stdlib.h>
#   include <string.h>
#   include <pthread.h>
#endif

#ifdef _DEBUG
#   include <assert.h>
#endif

#include "MemReleasePool.h"
#include "__internal/DLinkedList.h"
#include "__internal/InternalLock.h"

/**
 *  Autorelease mem pool structure
 * 
 *   +---+
 *   | T | (thread local storage index)
 *   +-|-+
 *     |
 *    \|/
 *   +---+    +---+    +---+
 *   |   |<---|   |<---|   |<---
 *   | P |    | P |    | P |     ...   (auto release pools)
 *   |   |--->|   |--->|   |--->
 *   +---+    +---+    +---+
 *  /|\  |   /|\  |   /|\  |
 *   |   |    |   |    |   |
 *   |  \|/   |  \|/   |  \|/
 *   +---+    +---+    +---+
 *   | M |    | M |    | M |           (mem nodes in each pool)
 *   +---+    +---+    +---+
 *  /|\  |   /|\  |   /|\  |
 *   |   |    |   |    |   |
 *   |  \|/   |  \|/   |  \|/
 *   +---+    +---+    +---+
 *   | M |    | M |    | M |
 *   +---+    +---+    +---+
 *  /|\  |   /|\  |   /|\  |
 *   |   |    |   |    |   |
 *   |  \|/   |  \|/   |  \|/
 *   +---+    +---+    +---+
 *   | M |    | M |    | M |
 *   +---+    +---+    +---+
 *  /|\  |   /|\  |   /|\  |
 *   |   |    |   |    |   |
 *   |  \|/   |  \|/   |  \|/
 *     .        .        .
 *     .        .        .
 *     .        .        .
 *    
 *
 *
 */
typedef struct _mempoolinfo
{
   dllist_t * pMemPoolHead;  ///< points to pool head
   dllist_t * pMemPoolTail;  ///< points to pool tail
   
} mempoolinfo_t;


/**
 *  @note
 *  Instance of release pool must not be passed across threads
 */
struct _memrelpool            // memrelpool_t
{
   dllist_t dlist;            ///< doubly linked list
   bool_t isLinked;           ///< whether this pool is linked in the pool group
   dllist_t * pMemNodeHead;   ///< points to mem node head
   dllist_t * pMemNodeTail;   ///< points to mem node tail
};

/**
 *  A memory node to allocate in addition to the normal memory block
 *
 *  <pre>
 *
 *  Allocated memory pattern:
 *
 *  memnode_t -- structure of memory node
 *  sig -- pattern signature (to check writing out of bound error)
 *  memory block -- memory requested by user
 *
 *
 *  pointer returned     pointer returned
 *  from calloc          to user
 *     |                 |
 *    \|/               \|/
 *    +-----------+-----+--------------------+-----+
 *    | memnode_t | sig |  memory block ...  | sig |
 *    +-----------+-----+--------------------+-----+
 *
 *
 *  For statData in memnode_t, 
 *       bit 31:      not used
 *       bit 20 ~ 30: weak reference token   (11 bits)
 *       bit 0  ~ 19: actual reference count (20 bits)
 *
 *  </pre>
 *
 */
typedef struct _memnode
{
    dllist_t dlist;   ///< doubly linked list
    memrelpool_t * pRelPool;  ///< the pool it belongs to 
    dtorfunc_t dtorFunc;      ///< destructor function
    int statData;     ///< statistical data
    sz_t size;        ///< size allocated
} memnode_t;


/**
 * thread local storage index so as to ensure thread safe
 */
#if defined(WIN32)
PRIVATE u32_t st_memPoolIndex = TLS_OUT_OF_INDEXES;         // memory pool
PRIVATE u32_t st_namedDataSlotIndex = TLS_OUT_OF_INDEXES;   // named data slot
#elif defined(linux)
#define TLS_OUT_OF_INDEXES ((pthread_key_t)0xFFFFFFFF)
PRIVATE pthread_key_t st_memPoolIndex = TLS_OUT_OF_INDEXES;
PRIVATE pthread_key_t st_namedDataSlotIndex = TLS_OUT_OF_INDEXES;
#endif


#ifdef _DEBUG
/**
 *  A mem pool used during debugging
 *  All memory blocks which are not added to a particular memory pool are added
 *  to this pool
 */
PRIVATE memrelpool_t st_debugMemPool = {0};
#endif

/**
 *  A process wide default memory pool. It is to chain up memory blocks used throughout
 *  the life of this process. This pool is freed at MRP_GlobalFinish.
 */
PRIVATE memrelpool_t * st_pDefaultMemPool = NULL;

/**
 * pattern signature embracing the allocated block (head and tail) 
 * to detect accessing out of bound.
 */
PRIVATE const u32_t st_sigPattern = 0xcdabcdab;

/**
 * extra size to allocate -- one memnode_t struct and 2 pattern signature
 */
#define MEM_EXTRASIZE (sizeof(memnode_t) + 2 * sizeof(u32_t))

/**
 *  Exit code when error occurs
 */
PRIVATE const int st_errProcessExitCode = 0x1023;

// forward declarations
PRIVATE void __MEMN_Unlink(memnode_t * pNode);
PRIVATE memrelpool_t * __MRP_GetCurMemPool(void);
PRIVATE sz_t __MRP_Aligned(sz_t size);
#if defined(WIN32)
PRIVATE void __MRP_PrintErr(const wc_t * szErr, memnode_t * pNode);
#elif defined(linux)
PRIVATE void __MRP_PrintErr(const u8_t * szErr, memnode_t * pNode);
#endif
PRIVATE mempoolinfo_t * __MRP_GetMemPoolInfo(void);


#ifdef linux
/******** functions using pthread_* to simulate Tls* *******/

PRIVATE pthread_key_t TlsAlloc()
{
   pthread_key_t key = TLS_OUT_OF_INDEXES;
   pthread_key_create(&key, NULL);
   return key;
}

PRIVATE bool_t TlsFree(pthread_key_t key)
{
   pthread_key_delete(key);
   return TRUE;
}

PRIVATE void * TlsGetValue(pthread_key_t key)
{
   return pthread_getspecific(key);
}

PRIVATE bool_t TlsSetValue(pthread_key_t key, const void *value)
{
   if (pthread_setspecific(key, value) == 0)
      return TRUE;

   return FALSE;
}

#endif


/*********  Private functions ***********/

PRIVATE u32_t * __MEMN_GetPtr2FrontPattern(memnode_t * pNode)
{
   return (u32_t*)((ptr_t)pNode + sizeof(memnode_t));
}

PRIVATE u32_t * __MEMN_GetPtr2RearPattern(memnode_t * pNode)
{
   return (u32_t*)((ptr_t)pNode + sizeof(memnode_t) + sizeof(u32_t) + __MRP_Aligned(pNode->size));
}

PRIVATE int __MEMN_GetReferenceCount(memnode_t * pNode)
{
   return (pNode->statData & 0xfffff);
}

PRIVATE void __MEMN_AssignReferenceCount(memnode_t * pNode, int c)
{
   pNode->statData &= ~0xfffff;
   pNode->statData |= c;
}

PRIVATE int __MEMN_GetWeakRefToken(memnode_t * pNode)
{
   return ((pNode->statData >> 20) & 0x7ff);
}

PRIVATE int __MEMN_NewWeakRefToken(memnode_t * pNode)
{
   PRIVATE int st_weakRefToken = 1;  // 0 is not used

   int curToken = st_weakRefToken;
   ++st_weakRefToken;
   if (st_weakRefToken > 0x7ff)
      st_weakRefToken = 1;

   pNode->statData &= ~0x7ff00000;
   pNode->statData |= (curToken << 20);

   return curToken;
}

/**
 *  Init a memory node and add pattern signautre, too
 *  
 *  @param [in] pRelPool -- release pool (may be NULL)
 *  @param [in] isNewAlloc -- TRUE if it is a new piece of memory
 */
PRIVATE void __MEMN_SubInitNode(memnode_t * pNode, sz_t size, memrelpool_t * pRelPool, bool_t isNewAlloc)
{
   u32_t *pPattern;

   if (!isNewAlloc) {
#ifdef _DEBUG
      //
      // in case this node is attached to the debug pool, which originally should
      // be a node not attached to any pool
      // 
      if (pNode->pRelPool == &st_debugMemPool)
         __MEMN_Unlink(pNode);
#endif 
      //
      // to prevent a node from attaching to more than one pool
      if (pNode->pRelPool != NULL)
         return;
   }
   
   pNode->pRelPool = pRelPool;
   if (__MEMN_GetReferenceCount(pNode) == 0)  // set ref count if 0
      __MEMN_AssignReferenceCount(pNode, 1);
   pNode->size = size;
   
   DL_InitDlList(&pNode->dlist);
   if (pRelPool != NULL) {
      /*
       * !! Important !!
       *
       * Add to list head so that the most recently allocated block will be freed first.
       * Some blocks allocated by third-party libraries wrapped with this cppflib may not get
       * freed because of the freeing sequence.
       */
      DL_AddEntryToHead(&pNode->dlist, &pRelPool->pMemNodeHead, &pRelPool->pMemNodeTail);
   }

   // add signautre before and after the block
   pPattern = __MEMN_GetPtr2FrontPattern(pNode);
   *pPattern = st_sigPattern;
   pPattern = __MEMN_GetPtr2RearPattern(pNode);
   *pPattern = st_sigPattern;
   
}

PRIVATE void __MEMN_InitNode(memnode_t * pNode, sz_t size, bool_t isAddToPool)
{
   memrelpool_t * pCurPool = (isAddToPool) ? __MRP_GetCurMemPool() : NULL;
#ifdef _DEBUG
   if (!pCurPool) // use debug pool if NULL
      pCurPool = &st_debugMemPool;
#endif
   __MEMN_SubInitNode(pNode, size, pCurPool, TRUE);
}

PRIVATE void __MEMN_ReInitNode(memnode_t * pNode, sz_t size, memrelpool_t * pRelPool)
{
   __MEMN_SubInitNode(pNode, size, pRelPool, FALSE);
}


/**
 *  Check pattern sigature
 *
 *  @return -1 -- front signature is corrupted
 *  @return 1 --  rear signature is corrupted
 *  @return 0 -- OK
 */
PRIVATE int __MEMN_ChkPatternSig(memnode_t * pNode)
{
   u32_t * pPattern;

   pPattern = __MEMN_GetPtr2FrontPattern(pNode);
   if (*pPattern != st_sigPattern)
      return -1;  // front corrupted

   pPattern = __MEMN_GetPtr2RearPattern(pNode);
   if (*pPattern != st_sigPattern)
      return 1;   // rear corrupted

   return 0;  // OK
}

/**
 *  Unlink from the list and check whether memory is accessed out of bound
 */
PRIVATE void __MEMN_Unlink(memnode_t * pNode)
{
   memrelpool_t * pRelPool = pNode->pRelPool;
   int ret = __MEMN_ChkPatternSig(pNode);
   if (ret != 0)
#if defined(WIN32) 
      __MRP_PrintErr(ret > 0 ? _S("rear corrputed") : _S("front corrupted"), pNode);
#elif defined(linux)
      __MRP_PrintErr(ret > 0 ? (const u8_t*)"rear corrputed" : (const u8_t*)"front corrupted", pNode);
#endif

   if (pRelPool != NULL) 
      DL_DeleteEntry(&pNode->dlist, &pRelPool->pMemNodeHead, &pRelPool->pMemNodeTail);

   pNode->pRelPool = NULL;
}

/**
 *  Increase reference count
 *
 *  @return new reference count
 */
PRIVATE int __MEMN_Retain(memnode_t * pNode)
{
   int c = __MEMN_GetReferenceCount(pNode) + 1;
   __MEMN_AssignReferenceCount(pNode, c);
   return c;
}

/**
 *  Decrease reference count
 *
 *  @return new reference count
 */
PRIVATE int __MEMN_Release(memnode_t * pNode)
{
   int c = __MEMN_GetReferenceCount(pNode) - 1;
   __MEMN_AssignReferenceCount(pNode, c);
   return c;
}

/**
 *  Check if a valid mem node block
 *
 *  @return -1 -- not a valid mem node block
 *  @return 0 -- OK
 */
PRIVATE int __MEMN_IsValidMemNodeBlock(memnode_t * pNode)
{
   u32_t * pPattern = __MEMN_GetPtr2FrontPattern(pNode);
   return (*pPattern == st_sigPattern) ? 0 : -1;
}

/**
 *  Clear this mem node block
 */
PRIVATE void __MEMN_ZeroMemNodeBlock(memnode_t * pNode)
{
   // clear the pattern
   u32_t * pPattern = __MEMN_GetPtr2FrontPattern(pNode);
   *pPattern = 0;

   // clear statistic data
   pNode->statData = 0;  // for safety, set this to 0 before freeing it

                         // FIXME: gcc may have a bug when calling 
                         //
                         //        memset(pNode, 0, sizeof(memnode_t));
                         //        
                         //        instead of setting a single field to 0
}

//////////////////////////////////////////////////////////////////////////////////////

/**
 *  Test whether thread local storage slot is ready
 */
PRIVATE bool_t __MRP_IsPoolSlotAvailable()
{
   return (st_memPoolIndex != TLS_OUT_OF_INDEXES);
}

/**
 *  Test whether named data slot is ready
 */
PRIVATE bool_t __MRP_IsNamedDataSlotAvailable()
{
   return (st_namedDataSlotIndex != TLS_OUT_OF_INDEXES);
}

/**
 *  Return aligned size (8 byte aligned)
 */
PRIVATE sz_t __MRP_Aligned(sz_t size)
{
#define ALIGNEDSIZE sizeof(ptr_t)

   sz_t s = size % ALIGNEDSIZE;
   return s ? (size - s + ALIGNEDSIZE) : size;
}

/**
 *  Get original allocated pointer
 *
 *  @param [in] ptr -- returned pointer to user program
 *
 *  @return pointer to start of original allocted buffer
 */
PRIVATE void * __MRP_GetOrgPtr(void *ptr)
{
   return (void*)((ptr_t)ptr - sizeof(u32_t) - sizeof(memnode_t));
}

/**
 *  Get pointer returned to user
 *
 *  @param [in] pOrgPtr -- pointer to start of original allocted buffer
 *
 *  @return returned pointer to user program  
 */
PRIVATE void * __MRP_GetReturnedPtr(void *pOrgPtr)
{
   return (void*)((ptr_t)pOrgPtr + sizeof(memnode_t) + sizeof(u32_t));
}

/**
 *  Get current release pool (i.e. at the tail)
 */
PRIVATE memrelpool_t * __MRP_GetCurMemPool(void)
{
   mempoolinfo_t * pPoolInfo = __MRP_GetMemPoolInfo();
   return (pPoolInfo) ? 
             (memrelpool_t*)GETORGPTR(memrelpool_t, dlist, pPoolInfo->pMemPoolTail) :
             (memrelpool_t*)NULL;
}

/**
 *  Print error message and exit program
 *
 *  Note: szErr shouldn't be too long
 */
#if defined(WIN32)
PRIVATE void __MRP_PrintErr(const wc_t * szErr, memnode_t * pNode)
{

#define LEN 300
#define MAXERRLEN 100
#define TLEN 8
#define MAXDATA 16

   wc_t errString[LEN], dataString[TLEN];
   u32_t i;
   u8_t * pData = (u8_t*)__MRP_GetReturnedPtr(pNode);
   int szErrLen = (int)wcslen(szErr);

   // error string
   szErrLen = szErrLen > MAXERRLEN ? MAXERRLEN : szErrLen;
   memcpy(errString, szErr, szErrLen * sizeof(wc_t));

   // extra info
   swprintf_s(&errString[szErrLen], LEN - szErrLen, _S("\r\naddress: 0x%p, memsize: %d\r\nfirst %d bytes: "), pData, pNode->size, MAXDATA);

   // data bytes
   for (i = 0; i < MAXDATA && i < pNode->size; ++i) {
      swprintf_s(dataString, TLEN, _S("0x%02x "), (u32_t)(*pData++));
      wcscat_s(errString, LEN, dataString);
   }
   MessageBox(NULL, errString, _S("MemReleasePool"), MB_ICONSTOP|MB_OK);
   ExitProcess(st_errProcessExitCode);
}
#elif defined(linux)
PRIVATE void __MRP_PrintErr(const u8_t * szErr, memnode_t * pNode)
{

#define LEN 300
#define MAXERRLEN 100
#define TLEN 8
#define MAXDATA 16

   u8_t errString[LEN], dataString[TLEN];
   u32_t i;
   u8_t * pData = (u8_t*)__MRP_GetReturnedPtr(pNode);
   int szErrLen = (int)strlen(szErr);

   // error string
   szErrLen = szErrLen > MAXERRLEN ? MAXERRLEN : szErrLen;
   memcpy(errString, szErr, szErrLen * sizeof(u8_t));
   
   // extra info
   sprintf(&errString[szErrLen], "\r\naddress: %p, memsize: %d\r\nfirst %d bytes: ", pData, pNode->size, MAXDATA);

   // data bytes
   for (i = 0; i < MAXDATA && i < pNode->size; ++i) {
      sprintf(dataString, "0x%02x ", (u32_t)(*pData++));
      strcat((char*)errString, (const char*)dataString);
   }

   strcat((char*)errString, (const char*)"\n");

   puts(errString);
   exit(st_errProcessExitCode);
}
#endif

/**
 *  Get meminfo structure
 */
PRIVATE mempoolinfo_t * __MRP_GetMemPoolInfo(void)
{
   return (mempoolinfo_t*)TlsGetValue(st_memPoolIndex);
}

/**
 *  Allocate memory
 *
 *  @param [in] isAddToPool -- true: add to release pool, false: don't add
 *  @param [in] dtorFunc -- destructor function if any
 */
PRIVATE void * __MRP_AllocateMem(sz_t num, sz_t size, 
                                 bool_t isAddToPool, dtorfunc_t dtorFunc)
{
   void *ptr = NULL;

   if ((ptr = calloc(__MRP_Aligned(num * size) + MEM_EXTRASIZE, sizeof(char)))) {
      __MEMN_InitNode((memnode_t*)ptr, num * size, isAddToPool);
      ((memnode_t*)ptr)->dtorFunc = dtorFunc; // assign destructor, too
      ptr = __MRP_GetReturnedPtr(ptr);
   }
   return ptr;
}

/**
 *  Test whether pool info is empty
 */
PRIVATE bool_t __MRP_IsMemPoolInfoEmpty(mempoolinfo_t * pPoolInfo)
{
   return (pPoolInfo->pMemPoolHead == NULL && pPoolInfo->pMemPoolTail == NULL);
}

/**
 *  Add pool to tail of pool group
 */
PRIVATE bool_t __MRP_AddPoolToTail(memrelpool_t * pRelPool)
{
   if (!pRelPool->isLinked) {
      mempoolinfo_t * pPoolInfo = __MRP_GetMemPoolInfo();
      if (pPoolInfo == NULL) { // create on demand
         pPoolInfo = (mempoolinfo_t*)calloc(1, sizeof(mempoolinfo_t));
         TlsSetValue(st_memPoolIndex, pPoolInfo); // add to thread local storage slot
      }

      DL_AddEntryToTail(&pRelPool->dlist, &pPoolInfo->pMemPoolHead, &pPoolInfo->pMemPoolTail);
      pRelPool->isLinked = TRUE;
      return TRUE;
   }

   return FALSE;
}

/**
 *  Unlink pool from pool group
 */
PRIVATE bool_t __MRP_UnlinkPoolFromGroup(memrelpool_t * pRelPool)
{
   if (pRelPool->isLinked) {
      mempoolinfo_t * pPoolInfo = __MRP_GetMemPoolInfo();
      DL_DeleteEntry(&pRelPool->dlist, &pPoolInfo->pMemPoolHead, &pPoolInfo->pMemPoolTail);
      pRelPool->isLinked = FALSE;
      return TRUE;
   }

   return FALSE;
}

/**
 *  This function is not exported and is intended to be used by Thread class only
 */
#if defined(WIN32)
u32_t
#elif defined(linux)
pthread_key_t 
#endif
MRP_GetNamedDataSlot()
{
   return st_namedDataSlotIndex;
}


/**
 *  Clean up named data storage for main thread
 */
PRIVATE void __MRP_CleanupNamedDataStorage()
{
   void * pSlotVal = TlsGetValue(MRP_GetNamedDataSlot());
   if (pSlotVal != NULL) {
      MRP_Release(pSlotVal);
      TlsSetValue(MRP_GetNamedDataSlot(), NULL);
   }
}

/******************** internal Public functions *******************/

/**
 *  Create a new auto release pool in the current thread becomes the active one automatically.
 */
PRIVATE memrelpool_t * __private_MRP_CreateRelPool()
{
   memrelpool_t * pRelPool = NULL;

   if (!__MRP_IsPoolSlotAvailable())
      return NULL;
   
   // new a pool 
   pRelPool = (memrelpool_t*)calloc(1, sizeof(memrelpool_t));
   // add it to tail
   __MRP_AddPoolToTail(pRelPool);

   return pRelPool;
}

/**
 *  Free the pool and the memory blocks attached to it 
 */
PRIVATE void __private_MRP_FreePool(memrelpool_t * pRelPool)
{
   mempoolinfo_t * pPoolInfo = NULL;
   dllist_t *pTra;
   memnode_t *pMemNode;
   int refCount;

   if (!pRelPool) return;

   // release all nodes in this pool
   pTra = pRelPool->pMemNodeHead;
   while (pTra) {
      pMemNode = (memnode_t*)GETORGPTR(memnode_t, dlist, pTra);
      refCount = MRP_Release(__MRP_GetReturnedPtr((void*)pMemNode));
      if (refCount > 0) {
         // still hanging around by some reference, clear linkage with the pool
         DL_DeleteEntry(&pMemNode->dlist, &pRelPool->pMemNodeHead, &pRelPool->pMemNodeTail);
         pMemNode->pRelPool = NULL;
#ifdef _DEBUG
         // add back to debug pool
         __MEMN_ReInitNode(pMemNode, pMemNode->size, &st_debugMemPool);
#endif
      }
      pTra = pRelPool->pMemNodeHead;
   }

   // release this pool finally
   __MRP_UnlinkPoolFromGroup(pRelPool);
   free(pRelPool);
   
   
   pPoolInfo = __MRP_GetMemPoolInfo();

   // delete pool info as well if it is the last pool
   if (pPoolInfo && __MRP_IsMemPoolInfoEmpty(pPoolInfo)) {
      free(pPoolInfo);
      TlsSetValue(st_memPoolIndex, NULL);
   }
}

/**
 *  Get number of nodes in a pool
 */
PRIVATE int __private_MRP_GetPoolNodesCount(memrelpool_t * pRelPool)
{
   int count = 0;
   dllist_t *pTra = pRelPool->pMemNodeHead;

   while (pTra) {
      ++count;
      pTra = DL_GetNextEntry(pTra);
   }
   
   return count;
}

/**
 *  Get number of pools created in the current thread
 */
PRIVATE int __private_MRP_GetPoolsCount()
{
   int count = 0;
   mempoolinfo_t *pPoolInfo;
   dllist_t *pTra;

   if (!__MRP_IsPoolSlotAvailable())
      return count;

   pPoolInfo = __MRP_GetMemPoolInfo();
   if (!pPoolInfo || __MRP_IsMemPoolInfoEmpty(pPoolInfo))
      return count;
   
   pTra = pPoolInfo->pMemPoolHead;
   while (pTra) {
      ++count;
      pTra = DL_GetNextEntry(pTra);
   }

   return count;
}

/**
 *  Decrement ref count by 1 and free it if ref count reaches 0
 *
 *  @return new ref count
 *  @return -1 on error
 */
PRIVATE int __private_MRP_Release(void * pMemLoc)
{
   int refCount = 0;
   memnode_t * pOrgNode;

   if (!pMemLoc) return -1;
   pOrgNode = (memnode_t*)__MRP_GetOrgPtr(pMemLoc);
   if (__MEMN_IsValidMemNodeBlock(pOrgNode) != 0) return -1;
   refCount = __MEMN_Release(pOrgNode);
   if (refCount <= 0) {
      if (pOrgNode->dtorFunc) // call destructor if any
         (*pOrgNode->dtorFunc)(pMemLoc);
      __MEMN_Unlink(pOrgNode);
#ifndef _DEBUG 
      __MEMN_ZeroMemNodeBlock(pOrgNode);
#endif
      free(pOrgNode);
   }

   return refCount;
}

/**
 *  Increase reference count by 1. Call this if you don't want a block to be freed
 *  during MRP_FreePool
 *
 *  @return new ref count
 *  @return -1 on error
 */
PRIVATE int __private_MRP_Retain(void * pMemLoc)
{
   if (!pMemLoc) return -1;
   pMemLoc = __MRP_GetOrgPtr(pMemLoc);
   if (__MEMN_IsValidMemNodeBlock((memnode_t*)pMemLoc) != 0) return -1;
   return __MEMN_Retain((memnode_t*)pMemLoc);
}

/**
 *  Detach itself from its pool. Call this if you want to pass a block to another thread
 *  and release it there.
 *
 *  @return itself
 */
PRIVATE void *__private_MRP_UnlinkFromPool(void * pMemLoc)
{
   memnode_t * pOrgNode;

   if (!pMemLoc) return NULL;
   pOrgNode = (memnode_t*)__MRP_GetOrgPtr(pMemLoc);
#ifdef _DEBUG
   if (pOrgNode->pRelPool == &st_debugMemPool)
      return pMemLoc;
#endif

   __MEMN_Unlink(pOrgNode);

#ifdef _DEBUG
   // add back to debug pool
   __MEMN_ReInitNode(pOrgNode, pOrgNode->size, &st_debugMemPool);
#endif
   return pMemLoc;
}

/**
 *  Reallocate memory with a new size
 */
PRIVATE void * __private_MRP_Realloc(void * pMemLoc, sz_t size)
{
   void * tPtr = pMemLoc ? __MRP_GetOrgPtr(pMemLoc) : NULL;

   if (!tPtr) {
      // tPtr is NULL, same as calloc
      tPtr = MRP_Calloc(1, size);
   }
   else {

      if (size == 0) { // equivalent to free         
         MRP_Release(pMemLoc);
         tPtr = NULL;
      }
      else {
         memnode_t * pNode = (memnode_t*)tPtr;
         memrelpool_t * pRelPool = pNode->pRelPool;
         int orgStat = pNode->statData;

         // unlink first since 'realloc' will probably return a different address
         // this will also check corrupted memory. too
         __MEMN_Unlink(pNode); 
         if ((tPtr = (void*)realloc(tPtr, __MRP_Aligned(size) + MEM_EXTRASIZE))) {
            pNode = (memnode_t*)tPtr; // reassign since it may have been changed
            pNode->statData = orgStat;

            __MEMN_ReInitNode(pNode, size, pRelPool);  // add back to original pool
            tPtr = __MRP_GetReturnedPtr(tPtr);
         }
      }
   }

   return tPtr;
}

/**
 *  Assign a new weak reference token if not set yet
 *
 *  @return the existing or newly-assigned weak reference token
 *  @return 0 -- error
 */
PRIVATE int __private_MRP_AssignWeakRefToken(void * pMemLoc)
{
   int token = 0;
   memnode_t * pOrgNode;

   if (!pMemLoc) return 0;
   pOrgNode = (memnode_t*)__MRP_GetOrgPtr(pMemLoc);
   if (__MEMN_IsValidMemNodeBlock(pOrgNode) != 0) return 0;
   token = __MEMN_GetWeakRefToken(pOrgNode);
   if (token == 0) { 
      token = __MEMN_NewWeakRefToken(pOrgNode);
   }
   return token;
}

/**
 *  Check if token is the same
 *
 *  @return TRUE -- the same
 */
PRIVATE bool_t __private_MRP_IsWeakRefTokenEqual(void * pMemLoc, int token)
{
   memnode_t * pOrgNode;

   if (!pMemLoc) return FALSE;
   pOrgNode = (memnode_t*)__MRP_GetOrgPtr(pMemLoc);
   if (__MEMN_IsValidMemNodeBlock(pOrgNode) != 0) return FALSE;
   return (__MEMN_GetWeakRefToken(pOrgNode) == token);
}

/**
 *  Add itself to an exisiting pool. Call this if you want to attach a block passed from
 *  another thread to a pool in the current thread.
 *
 *  @param [in] pMemLoc -- must be allocated from MRP_Calloc or 
 *                         has been detached from its original pool by MRP_UnlinkFromPool
 */
PRIVATE bool_t __private_MRP_AddToPool(void * pMemLoc, memrelpool_t *pRelPool)
{
   memnode_t * pOrgNode;

   if (!pMemLoc || !pRelPool) return FALSE;
   pOrgNode = (memnode_t*)__MRP_GetOrgPtr(pMemLoc);
   __MEMN_ReInitNode(pOrgNode, pOrgNode->size, pRelPool);
 
   return (pOrgNode->pRelPool == pRelPool);
}

/********************  Public functions **************************/


/**
 *  Initialize a thread local storage slot for every thread.
 *  Call once and only once in the main thread at the very beginning of a program
 */
_DLLAPI bool_t MRP_GlobalInit(void)
{
   // allocate thread local storage slot 
   if (!__MRP_IsPoolSlotAvailable()) {
      st_memPoolIndex = TlsAlloc();
      if (!__MRP_IsPoolSlotAvailable()) // error
         return FALSE;

      st_namedDataSlotIndex = TlsAlloc();
      if (!__MRP_IsNamedDataSlotAvailable()) // error
         return FALSE;

      INTL_InitAllLocks(); // init internal locks

#if _DEBUG
      memset(&st_debugMemPool, 0, sizeof(memrelpool_t));
#endif

      // init default memory pool
      st_pDefaultMemPool = (memrelpool_t*)calloc(1, sizeof(memrelpool_t));
   }


   return TRUE;
}

#if defined(_DEBUG)
//! these functions live inside CppMemPool.cpp
void __CMP_DestroyBaseObject(void *p);
#   if defined(WIN32)
void * __CMP_GetDestructorAddress(void *p);
#   endif
#endif

PRIVATE void __PrintDataBytes(u8_t *pData, int nrBytePrint)
{
   int i = 0;
   for (i = 0; i < nrBytePrint; ++i) {
      fprintf(stderr, "%02x ", (u32_t)(*pData++));
   }
}

/**
 *  Do the reverse in 'MRP_GlobalInit'
 */
_DLLAPI bool_t MRP_GlobalFinish(void)
{

   if (__MRP_IsPoolSlotAvailable()) {

      // free default pool first
      MRP_FreePool(st_pDefaultMemPool);

      // free named data storage for main thread
      __MRP_CleanupNamedDataStorage();

      TlsFree(st_namedDataSlotIndex); // free named data slot
      st_namedDataSlotIndex = TLS_OUT_OF_INDEXES;

      TlsFree(st_memPoolIndex); // free memory pool thread local storage
      st_memPoolIndex = TLS_OUT_OF_INDEXES;

#if _DEBUG

      if (st_debugMemPool.pMemNodeHead != NULL) {
         const sz_t MAXBYTEPRINT = 16;
         dllist_t *pTra = st_debugMemPool.pMemNodeHead;
         int leakCount = 0;

         fprintf(stderr, "***** Memory leaks *****\r\n");
         while (pTra) {
            memnode_t * pMemNode = (memnode_t*)GETORGPTR(memnode_t, dlist, pTra);
            int nrBytePrint = pMemNode->size > MAXBYTEPRINT ? MAXBYTEPRINT : (int)pMemNode->size;
            u8_t *pData = (u8_t*)__MRP_GetReturnedPtr((void*)pMemNode);


            if (pMemNode->dtorFunc == &__CMP_DestroyBaseObject) {
               // it is a BaseObject derived instance
#if defined(WIN32)
               fprintf(stderr, "\r\n%d)\t<instance> addr: 0x%p, <dtor> addr: 0x%p, memsize: %d, refcount: %d\r\n\t\tfirst %d bytes: ", 
                                      leakCount + 1, pData, __CMP_GetDestructorAddress(pData),
                                      pMemNode->size, __MEMN_GetReferenceCount(pMemNode), nrBytePrint);
#elif defined(linux)
               fprintf(stderr, "\r\n%d)\t<instance> addr: %p, memsize: %d, refcount: %d\r\n\t\tfirst %d bytes: ", 
                                      leakCount + 1, pData, pMemNode->size, __MEMN_GetReferenceCount(pMemNode), nrBytePrint);
#endif
            }
            else {
               // a normal memory block
               fprintf(stderr, "\r\n%d)\tblock addr: %p, memsize: %d, refcount: %d\r\n\t\tfirst %d bytes: ", 
                                      leakCount + 1, pData, pMemNode->size, __MEMN_GetReferenceCount(pMemNode), nrBytePrint);               
            }

            __PrintDataBytes(pData, nrBytePrint);

            ++leakCount;

            pTra = DL_GetNextEntry(pTra);
            
         }
         fprintf(stderr, "\r\n\r\n***** End of dumping [total: %d block(s)] *****\r\n", leakCount);

         assert(FALSE);
      }
#endif

      INTL_DeleteAllLocks(); // delete internal locks at the end
   }

   return TRUE;
}


/**
 *  Create a memory pool and attach it to the current thread
 */
_DLLAPI memrelpool_t * MRP_CreateRelPool(void)
{
   memrelpool_t *p = NULL;
   INTL_AcquireLock(LP_MEMPOOL);
   p = __private_MRP_CreateRelPool();
   INTL_ReleaseLock(LP_MEMPOOL);
   return p;
}

/**
 *  Free the pool as well as the memory blocks attached to it
 */
_DLLAPI void MRP_FreePool(memrelpool_t * pRelPool)
{
   INTL_AcquireLock(LP_MEMPOOL);
   __private_MRP_FreePool(pRelPool);
   INTL_ReleaseLock(LP_MEMPOOL);
}

/**
 *  Unlink pool from the thread it is attaching.
 *  This function is supposed for user to move a pool from one thread to another.
 *  
 *  @note This function should be rarely used. The memory blocks attaching will 
 *        also be moved as well so make sure you are doing it correctly.
 */
_DLLAPI bool_t MRP_UnlinkPoolFromGroup(memrelpool_t * pRelPool)
{
   bool_t isOK = FALSE;
   INTL_AcquireLock(LP_MEMPOOL);
   isOK = __MRP_UnlinkPoolFromGroup(pRelPool);
   INTL_ReleaseLock(LP_MEMPOOL);
   return isOK;
}

/**
 *  Add pool as current one in the current thread.
 *
 *  @note This function is supposed to be called after MRP_UnlinkPoolFromGroup.
 *        After MRP_UnlinkPoolFromGroup is called (unlink pool from its thread),
 *        call MRP_AddPoolAsCurrentPool in the target thread.
 */
_DLLAPI bool_t MRP_AddPoolAsCurrentPool(memrelpool_t * pRelPool)
{
   bool_t isOK = FALSE;
   INTL_AcquireLock(LP_MEMPOOL);
   if (__MRP_IsPoolSlotAvailable())
      isOK = __MRP_AddPoolToTail(pRelPool);
   INTL_ReleaseLock(LP_MEMPOOL);
   return isOK;
}

_DLLAPI int MRP_GetPoolNodesCount(memrelpool_t * pRelPool)
{
   int c = 0;
   INTL_AcquireLock(LP_MEMPOOL);
   c = __private_MRP_GetPoolNodesCount(pRelPool);
   INTL_ReleaseLock(LP_MEMPOOL);
   return c;
}

_DLLAPI int MRP_GetPoolsCount(void)
{
   int c = 0;
   INTL_AcquireLock(LP_MEMPOOL);
   c =__private_MRP_GetPoolsCount();
   INTL_ReleaseLock(LP_MEMPOOL);
   return c;
}

#ifdef _DEBUG
/**
 *  Get node count in debug pool
 */
_DLLAPI int MRP_GetDebugPoolNodesCount(void)
{
   int c = 0;
   INTL_AcquireLock(LP_MEMPOOL);
   c = __private_MRP_GetPoolNodesCount(&st_debugMemPool);
   INTL_ReleaseLock(LP_MEMPOOL);
   return c;
}
#endif

_DLLAPI int MRP_Release(void * pMemLoc)
{
   int c = 0;
   INTL_AcquireLock(LP_MEMPOOL);
   c = __private_MRP_Release(pMemLoc);
   INTL_ReleaseLock(LP_MEMPOOL);
   return c;
}

_DLLAPI int MRP_Retain(void * pMemLoc)
{
   int c = 0;
   INTL_AcquireLock(LP_MEMPOOL);
   c = __private_MRP_Retain(pMemLoc);
   INTL_ReleaseLock(LP_MEMPOOL);
   return c;
}

_DLLAPI void *MRP_UnlinkFromPool(void * pMemLoc)
{
   void *p = NULL;
   INTL_AcquireLock(LP_MEMPOOL);
   p = __private_MRP_UnlinkFromPool(pMemLoc);
   INTL_ReleaseLock(LP_MEMPOOL);
   return p;
}

_DLLAPI bool_t MRP_AddToPool(void * pMemLoc, memrelpool_t *pRelPool)
{
   bool_t isOK = FALSE;
   INTL_AcquireLock(LP_MEMPOOL);
   isOK = __private_MRP_AddToPool(pMemLoc, pRelPool);
   INTL_ReleaseLock(LP_MEMPOOL);
   return isOK;
}

/**
 *  Add itself to current pool
 *
 *  @param [in] pMemLoc -- must be allocated from MRP_Calloc or 
 *                         has been detached from its original pool by MRP_UnlinkFromPool
 */
_DLLAPI bool_t MRP_AddToCurPool(void * pMemLoc)
{
   bool_t isOK = FALSE;
   INTL_AcquireLock(LP_MEMPOOL);
   isOK = __private_MRP_AddToPool(pMemLoc, __MRP_GetCurMemPool());
   INTL_ReleaseLock(LP_MEMPOOL);
   return isOK;
}

/**
 *  Add to default memory pool
 */
_DLLAPI bool_t MRP_AddToDefaultPool(void * pMemLoc)
{
   bool_t isOK = FALSE;
   INTL_AcquireLock(LP_MEMPOOL);
   if (st_pDefaultMemPool)
      isOK = __private_MRP_AddToPool(pMemLoc, st_pDefaultMemPool);
   INTL_ReleaseLock(LP_MEMPOOL);
   return isOK;
}

/**
 *  Allocate memory without attaching to a auto release pool and retain count equals to 1 
 */
_DLLAPI void * MRP_Calloc(sz_t num, sz_t size)
{
   void *p = NULL;
   INTL_AcquireLock(LP_MEMPOOL);
   p = __MRP_AllocateMem(num, size, FALSE, NULL);
   INTL_ReleaseLock(LP_MEMPOOL);
   return p;
}

/**
 *  Allocate memory without attaching to a auto release pool and with an optional destructor function
 */
_DLLAPI void * MRP_CallocDtor(sz_t num, sz_t size, dtorfunc_t dtorFunc)
{
   void *p = NULL;
   INTL_AcquireLock(LP_MEMPOOL);
   p = __MRP_AllocateMem(num, size, FALSE, dtorFunc);
   INTL_ReleaseLock(LP_MEMPOOL);
   return p;
}

/**
 *  Allocate memory and add it to the current auto release pool
 */
_DLLAPI void * MRP_CallocARel(sz_t num, sz_t size)
{
   void *p = NULL;
   INTL_AcquireLock(LP_MEMPOOL);
   p = __MRP_AllocateMem(num, size, TRUE, NULL);
   INTL_ReleaseLock(LP_MEMPOOL);
   return p;
}

/**
 *  Allocate memory, add it to current auto release pool with an optional destructor function
 */
_DLLAPI void * MRP_CallocARelDtor(sz_t num, sz_t size, dtorfunc_t dtorFunc)
{
   void *p = NULL;
   INTL_AcquireLock(LP_MEMPOOL);
   p = __MRP_AllocateMem(num, size, TRUE, dtorFunc);
   INTL_ReleaseLock(LP_MEMPOOL);
   return p;
}

_DLLAPI void * MRP_Realloc(void * pMemLoc, sz_t size)
{
   void *p = NULL;
   INTL_AcquireLock(LP_MEMPOOL);
   p = __private_MRP_Realloc(pMemLoc, size);
   INTL_ReleaseLock(LP_MEMPOOL);
   return p;
}

/**
 *  Assign and return a new (or exisiting) token for a pointer with weak reference
 */
_DLLAPI int MRP_AssignWeakRefToken(void * pMemLoc)
{
   int token = 0;
   INTL_AcquireLock(LP_MEMPOOL);
   token = __private_MRP_AssignWeakRefToken(pMemLoc);
   INTL_ReleaseLock(LP_MEMPOOL);
   return token;
}

/**
 *  Check if the supplied token is equal to the one at pMemLoc
 */
_DLLAPI bool_t MRP_IsWeakRefTokenEqual(void * pMemLoc, int token)
{
   bool_t isOK = FALSE;
   INTL_AcquireLock(LP_MEMPOOL);
   isOK = __private_MRP_IsWeakRefTokenEqual(pMemLoc, token);
   INTL_ReleaseLock(LP_MEMPOOL);
   return isOK;
}